#pragma once

#include "../mqcommon/log.hpp"
#include "../mqcommon/helper.hpp"
#include "../mqcommon/msg.pb.h"

#include <iostream>
#include <sstream>
#include <mutex>
#include <memory>
#include <unordered_map>

namespace mymq
{
    struct Binding
    {
        using ptr = std::shared_ptr<Binding>;
        std::string exchange_name;
        std::string msgqueue_name;
        std::string binding_key;
        bool durable;
        Binding() = default;
        Binding(const std::string &ename, const std::string &qname, const std::string &key, bool bdurable)
            : exchange_name(ename), msgqueue_name(qname), binding_key(key), durable(bdurable)
        {
        }
    };

    class BindingMapper
    {
    public:
        // MsgQueue映射绑定信息
        using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
        // 交换机映射MsgQueueBindingMap
        using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;

    public:
        BindingMapper(const std::string &dbfile)
            : _sql_helper(dbfile)
        {
            if (!FileHelper(dbfile).Exists())
            {
                std::string ppath = FileHelper::ParentDirectory(dbfile);
                FileHelper::CreateDirectory(ppath);
            }
            assert(_sql_helper.Open());
            CreateTable();
        }
        void CreateTable()
        {
            // "create table if not exists binding_table(...);"
            std::stringstream ss;
            ss << "create table if not exists binding_table(";
            ss << "exchange_name varchar(128), ";
            ss << "msgqueue_name varchar(128), ";
            ss << "binding_key varchar(128), ";
            ss << "durable int);";
            bool ret = _sql_helper.Exec(ss.str().c_str(), nullptr, nullptr);
            if (ret == false)
            {
                FATAL_LOG("创建绑定数据库表失败");
                abort();
            }
        }
        void RemoveTable()
        {
            // "drop table if exists binding_table"
            bool ret = _sql_helper.Exec("drop table if exists binding_table;", nullptr, nullptr);
            if (ret == false)
            {
                FATAL_LOG("删除绑定数据库表失败");
                abort();
            }
        }
        void Insert(const Binding::ptr &bp)
        {
            // "insert into binding_table values(...);"
            std::stringstream ss;
            ss << "insert into binding_table values('";
            ss << bp->exchange_name << "', '";
            ss << bp->msgqueue_name << "', '";
            ss << bp->binding_key << "', ";
            ss << bp->durable << ");";
            bool ret = _sql_helper.Exec(ss.str().c_str(), nullptr, nullptr);
            if (ret == false)
            {
                ERROR_LOG("插入绑定数据库表数据失败");
            }
        }
        void Remove(const std::string &ename, const std::string &qname)
        {
            // "delete from binding_table where exchange_name='?' and msgqueue_name='?';"
            std::stringstream ss;
            ss << "delete from binding_table where exchange_name='";
            ss << ename << "' and ";
            ss << "msgqueue_name='" << qname << "';";
            bool ret = _sql_helper.Exec(ss.str().c_str(), nullptr, nullptr);
            if (ret == false)
            {
                ERROR_LOG("删除绑定数据库表数据失败");
            }
        }
        void RemoveExchangeBings(const std::string &ename)
        {
            // "delete from binding_table where exchange_name='?';
            std::stringstream ss;
            ss << "delete from binding_table where exchange_name='";
            ss << ename << "';";
            bool ret = _sql_helper.Exec(ss.str().c_str(), nullptr, nullptr);
            if (ret == false)
            {
                ERROR_LOG("删除绑定数据库表数据失败");
            }
        }
        void RemoveMsgQueueBinds(const std::string &qname)
        {
            // "delete from binding_table where msgqueue_name='?';
            std::stringstream ss;
            ss << "delete from binding_table where msgqueue_name='";
            ss << qname << "';";
            bool ret = _sql_helper.Exec(ss.str().c_str(), nullptr, nullptr);
            if (ret == false)
            {
                ERROR_LOG("删除绑定数据库表数据失败");
            }
        }
        BindingMap Recovery()
        {
            BindingMap result;
            bool ret = _sql_helper.Exec("select * from binding_table", SelectCallback, &result);
            if (ret == false)
            {
                ERROR_LOG("恢复绑定数据库表数据失败");
            }
            return result;
        }

    private:
        static int SelectCallback(void *arg, int numcol, char **row, char **fields)
        {
            BindingMap *result = (BindingMap *)arg;
            Binding::ptr bp = std::make_shared<Binding>(row[0], row[1], row[2], bool(std::stoi(row[3])));
            BindingMapper::MsgQueueBindingMap &mqbmap = (*result)[bp->exchange_name];
            mqbmap.insert(std::make_pair(bp->msgqueue_name, bp));
            return 0;
        }

    private:
        SqliteHelper _sql_helper;
    };

    class BindingManager
    {
    public:
        using ptr = std::shared_ptr<BindingManager>;
    public:
        BindingManager(const std::string &dbfile)
            : _mapper(dbfile), _bindings(_mapper.Recovery())
        {
        }
        bool Bind(const std::string &ename, const std::string &qname, const std::string &key, bool durable)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto it = _bindings.find(ename);
            if (it != _bindings.end() && it->second.find(qname) != it->second.end())
                return false;
            BindingMapper::MsgQueueBindingMap &mqbmap = _bindings[ename];
            Binding::ptr bp = std::make_shared<Binding>(ename, qname, key, durable);
            mqbmap.insert(std::make_pair(qname, bp));
            if (durable)
                _mapper.Insert(bp);
            return true;
        }
        bool UnBind(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto it = _bindings.find(ename);
            if (it == _bindings.end())
                return false;
            auto qit = it->second.find(qname);
            if (qit == it->second.end())
                return false;
            if (qit->second->durable)
                _mapper.Remove(ename, qname);
            it->second.erase(qit);
            return true;
        }
        void RemoveExchangeBindings(const std::string &ename)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto it = _bindings.find(ename);
            if (it == _bindings.end())
                return;
            _mapper.RemoveExchangeBings(ename);
            _bindings.erase(ename);
        }
        void RemoveMsgQueueBindings(const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            _mapper.RemoveMsgQueueBinds(qname);
            for (auto &binding : _bindings)
            {
                auto it = binding.second.find(qname);
                if (it == binding.second.end())
                    continue;
                binding.second.erase(qname);
            }
        }
        BindingMapper::MsgQueueBindingMap GetExchangeBindings(const std::string &ename)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            static const BindingMapper::MsgQueueBindingMap empty_map;
            auto it = _bindings.find(ename);
            return it != _bindings.end() ? it->second : empty_map;
            return it->second;
        }
        Binding::ptr GetBinding(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto it = _bindings.find(ename);
            if (it == _bindings.end())
                return Binding::ptr();
            auto iit = it->second.find(qname);
            if (iit == it->second.end())
                return Binding::ptr();
            return iit->second;
        }
        bool Exists(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            auto it = _bindings.find(ename);
            if (it == _bindings.end())
                return false;
            auto iit = it->second.find(qname);
            if (iit == it->second.end())
                return false;
            return true;
        }
        size_t Size()
        {
            std::unique_lock<std::mutex> lock(_mtx);
            size_t total_size = 0;
            for (auto &binding : _bindings)
            {
                total_size += binding.second.size();
            }
            return total_size;
        }
        void Clear()
        {
            std::unique_lock<std::mutex> lock(_mtx);
            _mapper.RemoveTable();
            _bindings.clear();
        }

    private:
        std::mutex _mtx;
        BindingMapper _mapper;
        BindingMapper::BindingMap _bindings;
    };
}